/**
 * Chat
 * @author Seth^^
 * @copyright 2012
 * @version: 1.2
 * @dependencies jquery 1.6.1, jquery UI 1.8.13, APE Client Lib, Mootools-Core, dateformat, linkify, jquery mousewheel, jquery mCustomScrollbar, Base64, titlealert
 */

var Chat = new Class({
	Extends: APE.Client,
	Implements: Options,
	options: {},

	// constructor function
	initialize: function(options) {
		this.setOptions(options);

		// vars used
		this.cache = {};
		this.loading = {};
		this.container = null;
		this.msg = null;
		this.sound = null;
		this.sound2 = null;
		this.pipe = null;
		this.delay = false;
		this.focus = false;

		// on receive and send events
		this.onRaw('kick', this.onKick);
		this.onRaw('data', this.rawData);
		this.onCmd('send', this.cmdSend);

		// on error events
		this.onError('004', this.reset);
		this.onError('006', this.getParticipant);
		this.onError('007', this.getParticipant);

		// on load, ready and pipe events
		this.addEvent('load', this.start);
		this.addEvent('ready', this.createChat);
		this.addEvent('multiPipeCreate', this.createPipe);

		// user events
		this.addEvent('userJoin', this.createUser);
		this.addEvent('userLeft', this.deleteUser);
	},

	// on kick
	onKick: function(raw) {
		// find correct user
		if(raw.data.user != this.filterId(this.core.user.properties.name))
			return false;

		// kick user
		this.pipe.left();

		// redirect page
		Petolio.showMessage(this.options.translate[17], function() {
			Petolio.go(document.location.href);
		});
	},

	// receive data
	rawData: function(raw, pipe) {
		// only show notifications if window is not focused
		if(this.focus === false) {
			// notify the browser window / tab
			jQuery.titleAlert(this.options.translate[13], {
				requireBlur: false,
				stopOnFocus: true,
				duration: 0,
				interval: 1000
			});
		}

		// notify by sound
		this.sound.play();
		this.sound2.src = this.sound2.title;

		// write message in chat
		this.writeMessage(pipe, raw.data.msg, raw.data.from);
	},

	// send data
	cmdSend: function(data, pipe) {
		// save message in log
		Petolio.ajax({
			url: 'chat/log',
			type: 'post',
			data: {user: this.filterId(this.core.user.properties.name), msg: data.msg, channel: this.options.channelId},
			cache: false,
			success: function(x) {
				// nothing here
			}
		});

		// write message in chat
		this.writeMessage(pipe, data.msg, this.core.user);
	},

	// write the message in chat
	writeMessage: function(pipe, message, from) {
		var timestamp = dateFormat(new Date(), "h:MM TT");

		// append message to last message
		if(pipe.lastMsg && pipe.lastMsg.from.pubid == from.pubid) {
			jQuery(pipe.lastMsg.el, '#ape').find('div.message').append('<span>' + this.parseMessage(message) + '</span><dfn>' + timestamp + '</dfn><div class="clear"></div>');

		// create new one
		} else {
			// inject message in the container
			var element = new Element('div', {'class': 'user', 'html':
				'<table class="loading user_'+ this.filterId(from.properties.name) +'">'+
					'<colgroup><col width="1%"><col></colgroup>'+
					'<tr>'+
						'<td valign="top" style="vertical-align: top;">'+
							'<div class="avatar">'+
								'<img src="/images/blank.gif" style="width: 50px; height: 33px; background: white url(\'/images/dashboard/loading.gif\') center center no-repeat;"/>'+
							'</div>'+
						'</td>'+
						'<td valign="top" style="vertical-align: top; padding-top: 3px;">'+
							'<a style="font-weight: bold;" href="javascript:void(0);" target="_blank" class="loading">' + this.options.translate[2] + '</a>'+
							'<div class="top"></div>'+
							'<div class="message"><span>' + this.parseMessage(message) + '</span><dfn>' + timestamp + '</dfn><div class="clear"></div></div>'+
						'</td>'+
					'</tr>'+
				'</table>'
			}).inject(pipe.dom.message);
			pipe.lastMsg = {from: from, el: element};

			// load users info
			this.setParticipant(this.filterId(from.properties.name), function (x) {
				// set details
				this.setDetails(this.filterId(from.properties.name), x);

				// reinit scroll
				this.scroll('#message', 'bottom');
			});
		}

		// reinit scroll
		this.scroll('#message', 'bottom');
	},

	// announce messages in chat
	writeAnnounce: function(pipe, message, from) {
		var timestamp = dateFormat(new Date(), "h:MM TT");

		// inject message in container
		new Element('div', {'class': 'user', 'html':
			'<table class="loading user_'+ this.filterId(from.properties.name) +'">'+
				'<colgroup><col width="1%"><col></colgroup>'+
				'<tr>'+
					'<td>'+
						'<div class="avatar">'+
							'<img src="/images/blank.gif" style="width: 50px; height: 33px; background: white url(\'/images/dashboard/loading.gif\') center center no-repeat;"/>'+
						'</div>'+
					'</td>'+
					'<td>'+
						'<div class="fivepx"></div>'+
						'<div class="announce">'+
							'<span><a style="font-weight: bold;" href="javascript:void(0);" target="_blank" class="loading">' + this.options.translate[2] + '</a>' + this.parseMessage(message) + '</span><dfn>' + timestamp + '</dfn><div class="clear"></div>'+
						'</div>'+
						'<div class="fivepx"></div>'+
					'</td>'+
				'</tr>'+
			'</table>'
		}).inject(pipe.dom.message);
		pipe.lastMsg = null;

		// load users info
		this.setParticipant(this.filterId(from.properties.name), function (x) {
			// set details
			this.setDetails(this.filterId(from.properties.name), x);

			// reinit scroll
			this.scroll('#message', 'bottom');
		});

		// perform scroll
		this.scroll('#message', 'bottom');
	},

	// history messages in chat
	writeHistory: function(data, pipe, self) {
		// define this var here
		var lastMsg = null;

		// clear messages
		jQuery(pipe.dom.message, '#ape').find('div.user').remove();

		// no data? skip
		if(!data.length > 0)
			return this.scroll('#message', false);

		// go through each message and inject them into message container
		jQuery.each(data, function(k, v) {
			// append message to last message
			if(lastMsg && lastMsg.from.user_id == v.user_id) {
				jQuery(lastMsg.el, '#ape').find('div.message').append('<span>' + self.parseMessage(v.message) + '</span><dfn>' + v.date_created + '</dfn><div class="clear"></div>');

			// create new one
			} else {
				// inject message in the container
				var element = new Element('div', {'class': 'user', 'html':
					'<table class="user_'+ v.user_id +'" style="opacity: 1;">'+
						'<colgroup><col width="1%"><col></colgroup>'+
						'<tr>'+
							'<td valign="top" style="vertical-align: top;">'+
								'<div class="avatar">'+
									'<img src="' + v.user_avatar + '" />'+
								'</div>'+
							'</td>'+
							'<td valign="top" style="vertical-align: top; padding-top: 3px;">'+
								'<a style="font-weight: bold;' + (self.options.owner == v.user_id ? ' color: red;' : '') + '" href="' + v.user_url + '" target="_blank">' + v.user_name + '</a>'+
								'<div class="top"></div>'+
								'<div class="message"><span>' + self.parseMessage(v.message) + '</span><dfn>' + v.date_created + '</dfn><div class="clear"></div></div>'+
							'</td>'+
						'</tr>'+
					'</table>'
				}).inject(pipe.dom.message);
				lastMsg = {from: v, el: element};
			}
		});

		// inject delimiter
		new Element('div', {'class': 'user', 'html': '<div class="delimiter"></div>'}).inject(pipe.dom.message);
		pipe.lastMsg = null;

		// perform scroll
		this.scroll('#message', 'bottom');
	},

	// parse message
	parseMessage: function(message) {
		// decode message
		message = this.decode(message);

		// htmlentities
		message = message
			.replace(/&/g, "&amp;")
			.replace(/</g, "&lt;")
			.replace(/>/g, "&gt;")
			.replace(/"/g, "&quot;")
			.replace(/'/g, "&#039;");

		// linkify message
		var self = this;
		message = linkify(message, {'callback': function(text, href) {
			return href ? '<a target="_blank" href="' + href + '" title="' + href + '">' + self.truncate(text, 70) + '<\/a>' : text;
		}});

		// smilify message
		message = this.emoticons(message);

		// return the message
		return message;
	},

	// truncate string
	truncate: function(fullStr, strLen, separator) {
		if (fullStr.length <= strLen)
			return fullStr;

		separator = separator || '&hellip;';

		var sepLen = separator.length,
			charsToShow = strLen - sepLen,
			frontChars = Math.ceil(charsToShow / 2),
			backChars = Math.floor(charsToShow / 2);

		return fullStr.substr(0, frontChars) + separator + fullStr.substr(fullStr.length - backChars);
	},

	// handle emoticons
	emoticons: function(text) {
		var emoticons = {
			// extra
			':))' : '-48px 0px', ':-))' : '-48px 0px',
			':((' : '-160px 0px', ':-((' : '-160px 0px',

			// regular
			':)' : '0px 0px', ':-)' : '0px 0px',
			':(' : '-16px 0px', ':-(' : '-16px 0px',
			':P' : '-32px 0px', ':-P' : '-32px 0px', ':p' : '-32px 0px', ':-p' : '-32px 0px',
			':D' : '-48px 0px', ':-D' : '-48px 0px', ':d' : '-48px 0px', ':-d' : '-48px 0px',
			':O' : '-64px 0px', ':-O' : '-64px 0px', ':o' : '-64px 0px', ':-o' : '-64px 0px',
			';)' : '-80px 0px', ';-)' : '-80px 0px',

			// love
			':*' : '-208px 0px', ':-*': '-208px 0px',
			'&lt;3': '-224px 0px'
		}, patterns = [], metachars = /[[\]{}()*+?.\\|^$\-,&#\s]/g;

		// build a regex pattern for each defined property
		for (var i in emoticons)
			if (emoticons.hasOwnProperty(i)) // escape metacharacters
				patterns.push('('+i.replace(metachars, "\\$&")+')');

		// build the regular expression and replace
		return text.replace(new RegExp(patterns.join('|'),'g'), function (match) {
			return typeof emoticons[match] != 'undefined' ?
				'<img title="'+ match +'" class="emote" src="/images/blank.gif" style="background-position: '+ emoticons[match] +'; " />' :
				match;
		});
	},

	// perform scrolling
	scroll: function(container, scroll) {
		// create scroll
		if(typeof scroll == 'undefined')
			return jQuery(container, '#ape').mCustomScrollbar({
				scrollInertia: 0
			});

		// update
		jQuery(container, '#ape').mCustomScrollbar("update");

		// scroll to ?
		if(scroll != false)
			// only scroll to if we have a scroll bar
			if(!(jQuery(container, '#ape').find('div.mCS_no_scrollbar').length > 0))
				// don't scroll if we're already dragging
				if(!(jQuery(container, '#ape').find('div.ui-draggable-dragging').length > 0))
					// perform scroll
					jQuery(container, '#ape').mCustomScrollbar("scrollTo", scroll);
	},

	// on bad session reset it
	reset: function() {
		this.core.clearSession();
		if(this.container)
			this.container.dispose();

		this.core.initialize(this.core.options);
	},

	// get participant and handle errors
	getParticipant: function(error) {
		// multi join?
		if (error) {
			if (error.data.code == 007) {
				jQuery('#chat').html('<b class="red bigger">' + this.options.translate[0] + '</b>');
				return Petolio.showMessage(this.options.translate[1]);
			}
		}
	},

	// make user id unique per channel
	uniqId: function(u) {
		return this.options.channelId + '9e10e' + u;
	},

	// return user id without the channel
	filterId: function(u) {
		return u.split('9e10e')[1];
	},

	// setter and getter of our participant details and storing it in a cache for later use whilst performing the function callback 'f'
	setParticipant: function(id, f) {
		// stored in cache?
		if(this.cache[id] !== undefined)
			f.apply(this, [this.cache[id]]);

		// no? perform the request and store it
		else {
			// dont make the same request again
			if(this.loading[id] !== undefined)
				return;

			// set as loading already
			this.loading[id] = true;

			// perform ajax
			var self = this;
			Petolio.ajax({
				url: 'chat/participant',
				type: 'post',
				data: {id: id},
				cache: false,
				success: function (x) {
					self.cache[id] = x;
					f.apply(self, [self.cache[id]]);
				}
			});
		}
	},

	// set participant details
	setDetails: function(u, x) {
		jQuery('div.avatar > img', '#ape table.user_' + u).attr('src', x.avatar).removeAttr('style');
		jQuery('a:first', '#ape table.user_' + u).attr('href', x.url).html(x.name).removeAttr('class');
		jQuery('div.desc > span', '#ape table.user_' + u).html(x.type);
		jQuery('table.user_' + u, '#ape').removeClass('loading').css({"opacity": "1"});

		// special treatment for the owner :)
		if(this.options.owner == u)
			jQuery('a:first', '#ape table.user_' + u).css({'color': 'red'});
	},

	// start is called on load
	start: function() {
		this.core.start({'name': this.uniqId(this.options.name)});
	},

	// create chat is called on ready
	createChat: function() {
		// insert ape
		this.container = new Element('div', {'id': 'ape'});
		this.container.inject(this.options.container);

		// insert msg
		this.msg = new Element('div', {'id': 'msg', 'html': '<b class="red bigger">' + this.options.translate[17] + '</b>'});
		this.msg.inject(this.container);

		// insert sound
		this.sound = new Element('audio', {'src': '/sounds/chat.wav', 'preload': 'auto', 'autobuffer': ''});
		this.sound.inject(this.container);

		// ie sound
		this.sound2 = new Element('bgsound', {'title': '/sounds/chat.wav'});
		this.sound2.inject(this.container);

		// add delay
		var self = this;
		window.setTimeout(function() {
			self.delay = true;
		}, 1000);

		// add interval
		window.setInterval(function() {
			// window has focus?
			jQuery(window).focus(function() {
				self.focus = true;
			}).blur(function() {
				self.focus = false;
			});

			// no loading table found? return here
			if(!jQuery('table.loading', '#ape').length > 0)
				return;

			// go through each user that wasn't loaded
			jQuery.each(jQuery('table.loading', '#ape'), function() {
				var u = jQuery(this, '#ape').attr('class').split('_')[1];

				// load users info
				self.setParticipant(u, function (x) {
					// set details
					self.setDetails(u, x);

					// reinit scrolls
					self.scroll('#users', false);
					self.scroll('#message', 'bottom');
				});
			});
		}, 1000);
	},

	// create the multi pipe
	createPipe: function(pipe, options) {
		// set current pipe
		this.pipe = pipe;

		// remove message from chat
		this.msg.dispose();

		// define the pipe object that contains dom element
		pipe.dom = {};

		// create pipe container
		pipe.dom.container = new Element('div', {'class': 'pipe'}).inject(this.container);

		// create message container
		pipe.dom.message = jQuery(new Element('div', {'id': 'message', 'html':
			'<div class="inside">'+
				'<div class="history">'+
					'<span>' + this.options.translate[8] + ' <a href="javascript:void(0);" id="history_1">' + this.options.translate[9] + '</a> &bull; <a href="javascript:void(0);" id="history_7">' + this.options.translate[10] + '</a> &bull; <a href="javascript:void(0);" id="history_30">' + this.options.translate[11] + '</a> &bull; <a href="javascript:void(0);" id="history_90">' + this.options.translate[12] + '</a></span>'+
				'</div>'+
			'</div>'
		}).inject(pipe.dom.container)).find('div.inside').get(0);

		// If pipe has a user list create the users container
		if(pipe.users)
			pipe.dom.users = jQuery(new Element('div', {'id': 'users', 'html': '<div class="inside"></div>'}).inject(pipe.dom.container)).find('div.inside').get(0);

		// create the post container
		pipe.dom.post = new Element('div', {'class': 'post normal'}).inject(pipe.dom.container);

		// create the textarea and inject it into the post container
		pipe.dom.textarea = new Element('textarea', {
			'name': 'chat_keypresser',
			'autocomplete': 'off',
			'style': 'height: 15px;',
			'html': this.options.translate[4],
			'events': {
				'focus': function(ev, pipe) {
					if(pipe.dom.textarea.get('value') == this.options.translate[4])
						pipe.dom.textarea.set('value', '');

					pipe.dom.post.removeClass('error').addClass('normal');
				}.bindWithEvent(this, [pipe]),
				'blur': function(ev, pipe) {
					if(!pipe.dom.textarea.get('value'))
						pipe.dom.textarea.set('value', this.options.translate[4]);

					pipe.dom.post.removeClass('error').addClass('normal');
				}.bindWithEvent(this, [pipe]),
				'keydown': function(ev, pipe) { this.keyPress(ev, pipe, this); }.bindWithEvent(this, [pipe]),
			}
		}).inject(pipe.dom.post);

		// create the emoticon button and inject it into the post container
		pipe.dom.emoticons = new Element('div', {
			'class': 'pop',
			'html':
				'<div class="list">'+
					'<div class="content">'+
						'<img title=":)" class="emote" src="/images/blank.gif" style="background-position: 0px 0px;" />'+
						'<img title=":(" class="emote" src="/images/blank.gif" style="background-position: -16px 0px;" />'+
						'<img title=":P" class="emote" src="/images/blank.gif" style="background-position: -32px 0px;" />'+
						'<img title=":D" class="emote" src="/images/blank.gif" style="background-position: -48px 0px;" />'+
						'<img title=":O" class="emote" src="/images/blank.gif" style="background-position: -64px 0px;" />'+
						'<img title=";)" class="emote" src="/images/blank.gif" style="background-position: -80px 0px;" />'+
						'<img title=":((" class="emote" src="/images/blank.gif" style="background-position: -160px 0px;" />'+
						'<img title=":*" class="emote" src="/images/blank.gif" style="background-position: -208px 0px;" />'+
						'<img title="&lt;3" class="emote" src="/images/blank.gif" style="background-position: -224px 0px;" />'+
					'</div>'+
					'<div class="bottom"></div>'+
				'</div>'+
				'<div class="img">'+
					'<img title="' + this.options.translate[5] + '" class="emote" src="/images/blank.gif" style="background-position: -320px 0px;" />'+
				'</div>'
		}).inject(pipe.dom.post);

		// clear float to be compliant :)
		new Element('div', {'class': 'clear'}).inject(pipe.dom.post);

		// bind history clicks and automatically click yesterday
		var self = this;
		jQuery('div.history', '#ape').delegate('a', 'click', function(ev) { self.loadHistory(ev, pipe, this); });
		jQuery('#history_1', '#ape').click();

		// the event functions
		var _show = function() {
			jQuery(pipe.dom.emoticons, '#ape').find('div.list').show();
			jQuery(pipe.dom.emoticons, '#ape').find('div.img').css('opacity', 1);
		}, _hide = function() {
			jQuery(pipe.dom.emoticons, '#ape').find('div.list').hide();
			jQuery(pipe.dom.emoticons, '#ape').find('div.img').css('opacity', 0.5);
		}, _click = function() {
			pipe.dom.textarea.focus();
			pipe.dom.textarea.set('value', pipe.dom.textarea.get('value') + ' ' + jQuery(this, '#ape').attr('title'));
		};

		// add events to emoticons
		jQuery(pipe.dom.emoticons, '#ape').delegate('div.img', 'mouseover', _show);
		jQuery(pipe.dom.emoticons, '#ape').delegate('div.list', 'mouseover', _show);
		jQuery(pipe.dom.emoticons, '#ape').delegate('div.img', 'mouseout', _hide);
		jQuery(pipe.dom.emoticons, '#ape').delegate('div.list', 'mouseout', _hide);
		jQuery(pipe.dom.emoticons, '#ape').delegate('div.content > img', 'click', _click);

		// init scroll
		this.scroll('#message');
		this.scroll('#users');
	},

	// load history
	loadHistory: function(ev, pipe, self) {
		// based on class load what we need
		var a = jQuery(self, '#ape'),
			h = a.attr("id"),
			s = a.closest('span');

		// already loading? stop it right there buster
		if(s.hasClass('loading'))
			return false;

		// unbold all
		s.find('a').removeClass('active');

		// show loading
		s.addClass('loading');

		// perform ajax
		var self = this;
		Petolio.ajax({
			url: 'chat/get',
			type: 'post',
			data: {channel: this.options.channelId, history: h},
			cache: false,
			success: function (x) {
				s.removeClass('loading');
				a.addClass('active');

				self.writeHistory(x.history, pipe, self);
			}
		});
	},

	// on textarea key press
	keyPress: function(ev, pipe, self) {
		// overwrite
		pipe.dom.post.removeClass('error').addClass('normal');

		// jquery style
		var txt = jQuery(pipe.dom.textarea);

		// on enter
		if(ev.code == 13) {
			txt.val(txt.val().trim());
			// emoty?
			if(txt.val().length == 0) {
				pipe.dom.post.removeClass('normal').addClass('error');
				txt.val('');
				window.setTimeout(function() {
					txt.val('');
				}, 1);

				return false;
			}

			// send
			txt.focus();
			pipe.send(self.encode(txt.val()));
			txt.val('');
			window.setTimeout(function() {
				txt.val('');
			}, 1);
		}
	},

	// encode the message being sent
	encode: function(msg) {
		return Base64.encode(msg).replace('==', '1e2e').replace('=', '3e4e').replace('+', '5e6e').replace('/', '7e8e');
	},

	// decode the message being received
	decode: function(msg) {
		return Base64.decode(msg.replace('1e2e', '==').replace('3e4e', '=').replace('5e6e', '+').replace('7e8e', '/'));
	},

	// on user create
	createUser: function(user, pipe) {
		// create the user container (owner must be first)
		user.container = new Element('div', {'class': 'user'}).inject(pipe.dom.users, this.options.owner == this.filterId(user.properties.name) ? 'top' : 'bottom');

		// only announce after delay
		if(this.delay)
			this.writeAnnounce(pipe, this.encode(' ' + this.options.translate[6]), user);

		// inject new user in the container
		var one = new Element('table', {'class': 'loading user_' + this.filterId(user.properties.name), 'html':
			'<colgroup><col width="1%"><col></colgroup>'+
			'<tr>'+
				'<td valign="top" style="vertical-align: top;">'+
					'<div class="avatar">'+
						'<img src="/images/blank.gif" style="width: 50px; height: 33px; background: white url(\'/images/dashboard/loading.gif\') center center no-repeat;"/>'+
					'</div>'+
				'</td>'+
				'<td>'+
					'<a style="font-weight: bold;" href="javascript:void(0);" target="_blank" class="loading">' + this.options.translate[2] + '</a>'+
					'<div class="desc">'+
						'<span>' + this.options.translate[3] + '</span>'+
						(this.options.owner == this.options.name && this.options.owner != this.filterId(user.properties.name) ? ' &bull; <a href="javascript:void(0);" class="kick">' + this.options.translate[14] + '</a>' : '')+
					'</div>'+
				'</td>'+
			'</tr>'
		}).inject(user.container);

		// load users info
		this.setParticipant(this.filterId(user.properties.name), function (x) {
			// set details
			this.setDetails(this.filterId(user.properties.name), x);

			// reinit scroll
			this.scroll('#users', false);
		});

		// kick event
		var self = this;
		jQuery(one, '#ape').delegate('a.kick', 'click', function() {
			// get user id
			var u = jQuery(this, '#ape').closest('table').attr('class').split('_')[1];

			// show confirm
			Petolio.showConfirm(self.options.translate[15].replace('%s', jQuery(this, '#ape').closest('table').find('a:first').html()), function() {
				// perform ajax
				Petolio.ajax({
					url: 'chat/kick',
					type: 'post',
					data: {channel: self.options.channelId, user: u},
					cache: false,
					success: function (x) {
						// show error when unable to kick
						if(x.msg.data.value != 'ok')
							return Petolio.showMessage(self.options.translate[16]);
					}
				});
			});
		});

		// reinit scroll
		this.scroll('#users', false);
	},

	// on user delete
	deleteUser: function(user, pipe) {
		// remove user and announce it
		user.container.dispose();
		this.writeAnnounce(pipe, this.encode(' ' + this.options.translate[7]), user);

		// reinit scroll
		this.scroll('#users', false);
	}
});